using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using eLibrary;
using eLibrary.Constant;
using eLoop;
using ePipe;
using eTask;
using Kcptun = Letter.Net.Kcp.Library.KCP.KCP;

namespace Letter.Net.Kcp;

sealed partial class KcpSession : IKcpSession
{
    private static readonly Dictionary<KcpMode, Func<int, bool>> checkFunc = new()
    {
        {KcpMode.PACKAGE, length => { return length > 1024;} },
        {KcpMode.STREAM, length => { return true;} },
    };
    
    public KcpSession(int connectId, uint conv, IUdpSender udpSender, AConnection connection, EndPoint localEndPoint, EndPoint remoteEndPoint, KcpTransportOptions options, Func<IPipe<byte>> channelProvider)
    {
        this.Id = StringIdGenerator.GetNextId();
        this.Conv = conv;
        this.ConnectId = connectId;
        this.LocalEndPoint = localEndPoint;
        this.RemoteEndPoint = remoteEndPoint;
        this.udpSender = udpSender;
        this.connection = connection;
        connection.Init(this);
        
        this.checkLengthFunc = checkFunc[options.mode];
        
        this.kcp = new Kcptun(conv, this.OnKcpRawSend);
        this.kcp.SettingDelay(options.delay);
        this.kcp.SetStreamMode(options.mode == KcpMode.STREAM);
        
        this.kcpRcver = new KcpSessionRcver(this, channelProvider);
        this.kcpSnder = new KcpSessionSnder(this, options.mode, channelProvider);

        this.Transport = Pipeline.CreateDuplexPipe<byte>(this.kcpRcver.OutputReader, this.kcpSnder.InputWriter);
    }

    private Kcptun kcp;
    private IUdpSender udpSender;
    private AConnection connection;
    private KcpSessionRcver kcpRcver;
    private KcpSessionSnder kcpSnder;
    private uint mNextUpdateTime = 0;
    private long isActive = Booleans.FALSE;
    private Func<int, bool> checkLengthFunc;


    public uint Conv { get; }

    public int ConnectId { get; }

    public string Id { get; }

    public bool Active
    {
        get { return Interlocked.Read(ref isActive) == Booleans.TRUE; }
    }
    public EndPoint LocalEndPoint { get; }
    public EndPoint RemoteEndPoint { get; }
    public MemoryPool<byte> MemoryPool { get; }
    public IDuplexPipe<byte> Transport { get; }
    
    public ValueTask StartAsync()
    {
        if (Interlocked.CompareExchange(ref this.isActive, Booleans.TRUE, Booleans.FALSE) == Booleans.FALSE)
        {
            Scheduler.AddThreadRunner(OnUpdate);
            this.kcpSnder.Start();
            this.connection.OnActive();
        }
        
        return ValueTasks.CompletedTask;
    }

    

    public void OnRcvUdpMessage(ref ReadOnlySequence<byte> buf)
    {
        this.kcpRcver.InputUdpMessage(ref buf);
    }

    public void OnUdpException(Exception ex)
    {
        this.connection.OnException(ex);
    }

    private void OnKcpRawSend(byte[] data, int length)
    {
        this.kcpSnder.OnKcpRawSend(data, length);
    }
    
    private void OnUpdate()
    {
        if (0 == this.mNextUpdateTime || this.kcp.CurrentMS >= this.mNextUpdateTime)
        {
            this.kcp.Update();
            this.mNextUpdateTime = this.kcp.Check();
        }
    }
    
    private void WriteUdpMessage(MessageType type, ref ReadOnlySequence<byte> buf)
    {
        this.udpSender.SendMessage(this.RemoteEndPoint, type, this.ConnectId, ref buf);
    }
    
    public async ValueTask CloseAsync()
    {
        if (Interlocked.CompareExchange(ref this.isActive, Booleans.FALSE, Booleans.TRUE) == Booleans.TRUE)
        {
            await this.kcpRcver.ReleaseAsync();
            await this.kcpSnder.ReleaseAsync();
            // Scheduler.TryThreadRunnerRemoveListener(OnUpdate);
            this.connection.OnInactive();
        }
    }
}